package com.nage.input;

import java.util.ArrayList;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

import com.nage.engine.GameEngine;
import com.nage.graphics.RenderSystem;
import com.nage.multiplayer.MultiplayerSystem;
import com.nage.multiplayer.RemoteInput;

public class InputSystem implements OnTouchListener, SensorEventListener {

	private static Input input; // holds information about all input since it was last queried.
					
	boolean updated;
	private MultiplayerSystem ms;
	private boolean multiplayerActive;
	private long timeOfLastSend;

	private static ArrayList<InputResponder> m_Responders; // Array of objects that have been registered
															// to respond to Input events.
	private static int m_NumberToClear;
	private static boolean m_ResetList;
	private static ArrayList<InputResponder> m_RespondersToAdd;
	
	private static InputSystem m_Instance; // Singleton instance of InputSystem.
						

	private InputSystem() { }
	
	public static InputSystem getInstance() {
		if(m_Instance==null) {
			m_Instance = new InputSystem();
			m_Responders = new ArrayList<InputResponder>();
			m_RespondersToAdd = new ArrayList<InputResponder>();
			m_NumberToClear = 0;
			m_ResetList = false;
			input = new Input();
			input.updated = false;
		}
		return m_Instance;
	}
	
	@Override
	public boolean onTouch(View v, MotionEvent event) {

		buildInputEvent(event);
		deliverToResponders(input);
		deliverToMultiplayerSystem(input);	
		
		// Sleep for a bit to stop the flood of inputs.
		try {
			Thread.sleep(50);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		return true;
	}

	private void buildInputEvent(MotionEvent e) {
		// First touch of screen, record point of this touch in Down.
		if (e.getAction() == MotionEvent.ACTION_DOWN) {
			input.xDown = e.getX() / RenderSystem.view[2];
			input.yDown = (RenderSystem.view[3] - e.getY())
					/ RenderSystem.view[3];
			input.downEvent = true;

		} else {
			input.downEvent = false;
		}
		// record the current position of the input and vector between that
		// and down.
		input.changed = false;
		input.setX(e.getX() / RenderSystem.view[2]);
		input.setY((RenderSystem.view[3] - e.getY()) / RenderSystem.view[3]);

		input.xVector = input.x - input.xDown;
		input.yVector = input.y - input.yDown;
		input.time = System.currentTimeMillis();
		updated = true;		
	} 

	public void deliverToResponders(Input i) {
		// Send the input object to each registered responder.
		for(InputResponder r : m_Responders) {
			if(r.getOwner() == MultiplayerSystem.DEFAULT ) {
				Log.d("InputSystem", "Responder owned by DEFAULT (r: "+r.getOwner()+", default: "+MultiplayerSystem.DEFAULT+")");
				r.onInputEvent(i);
				continue;
			}
			if( i.owner==r.getOwner() ) {
				Log.d("InputSystem", "Responder owner matches input.(r: "+r.getOwner()+", input: "+i.owner+")");
				r.onInputEvent(i);
			}
		}
		
		if(m_ResetList) {
			for(int j = 0; j < m_NumberToClear; j++) {
				m_Responders.remove(0);
			}
			m_NumberToClear = 0;
			m_ResetList = false;
			m_Responders.trimToSize();
		}
		
		if(!m_RespondersToAdd.isEmpty()) {
			for (InputResponder r : m_RespondersToAdd) {
				m_Responders.add(r);
				m_NumberToClear++;
			}
			m_RespondersToAdd.clear();
		}
		
	
	}

	private void deliverToMultiplayerSystem(Input i) {
		// Send input to remote devices (if one hasn't been sent in the last 100ms)
		if (multiplayerActive && i.time - timeOfLastSend > 100) {
			i.owner = ms.getRole();
			ms.send(new RemoteInput(i));
			timeOfLastSend = i.time;
		}
	}

	public Input getInput() {
		// first send one input, then the other.
		if (updated) {
			updated = false;
			return input;
		} else {
			return null;
		}
	}

	public void link(GameEngine ge, boolean activateMultiplayer) {
		multiplayerActive = activateMultiplayer;
		ms = ge.ms;
	}
	
	/**
	 * Register an object that implements the InputResponder interface so that
	 * it will recieve input events from the input system.
	 * @param r A game object that wants to respond to user input.
	 */
	public static void registerResponder(InputResponder r) {
		m_RespondersToAdd.add(r);
	}
	
	/**
	 * Called when the simulation system is loading a new game graph. This will
	 * remove everything in the old graph so those objects don't recieve inputs.
	 * As this is often called in response to a touch event, we cannot instantly
	 * clear the list, as we are still iterating through it. Therefore, we record
	 * the current number of elements, then when we are out of the iteration loop
	 * we can remove that many elements from the start of the list.
	 * @return 
	 */
	public static void clearResponders() {
		m_ResetList = true;
	}

	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
		// TODO Auto-generated method stub
		
	}

	/**
	 * Called ever time an acceleromter event happens (if input system is registered for accel).
	 * Needs to convert x,y,z values into something useful in the Input object.
	 */
	@Override
	public void onSensorChanged(SensorEvent event) {
		Log.d("InputSystem", "Accel: x("+event.values[0]+") y("+event.values[1]+") z("+event.values[2]+")");
		input.xAngle = -event.values[0];
		input.yAngle = -event.values[1];
		deliverToResponders(input);

	}

}
